﻿namespace Deppton.Model.Xml
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Xml.Linq;

    /// <summary>
    /// Xml helper for survey definitions.
    /// </summary>
    public static class SurveyDefinitionXmlHelper
    {
        /// <summary>
        /// Reads the survey definition Xml element.
        /// </summary>
        /// <param name="element">The survey definition Xml element.</param>
        /// <returns>The survey definition.</returns>
        public static SurveyDefinition ReadSurveyDefinitionElement(XElement element)
        {
            var surveyDefinition = new SurveyDefinition();

            surveyDefinition.Id = element.Attribute("id").Value;
            surveyDefinition.Description = element.Attribute("description").Value;
            surveyDefinition.CompletedConditionRulesetId = element.Attribute("completedConditionRulesetId") != null ? element.Attribute("completedConditionRulesetId").Value : null;

            foreach (var questionElement in element.Element("questions").Elements("question"))
            {
                var question = new Question();

                question.Id = questionElement.Attribute("id").Value;
                question.Description = questionElement.Element("description").Value;
                question.Kind = (QuestionKind)Enum.Parse(typeof(QuestionKind), questionElement.Attribute("kind").Value, true);
                question.Required = questionElement.Attribute("required") != null ? Convert.ToBoolean(questionElement.Attribute("required").Value, CultureInfo.InvariantCulture) : false;
                question.Precondition = questionElement.Attribute("precondition") != null ? questionElement.Attribute("precondition").Value : null;
                question.Condition = questionElement.Attribute("condition") != null ? questionElement.Attribute("condition").Value : null;

                if (questionElement.Element("options") != null)
                {
                    question.Options = questionElement.Element("options").Elements("option").Select(o => new QuestionOption { Key = o.Attribute("value").Value, Description = o.Value }).ToArray();
                }

                surveyDefinition.Questions.Add(question);
            }

            foreach (var resultElement in element.Element("results").Elements("result"))
            {
                surveyDefinition.ResultDefinitions.Add(ReadResultDefinitionElement(resultElement));
            }

            foreach (var rulesetElement in element.Element("rulesets").Elements("ruleset"))
            {
                surveyDefinition.RulesetDefinitions.Add(ReadRulesetDefinitionElement(rulesetElement));
            }

            return surveyDefinition;
        }

        /// <summary>
        /// Reads the result definition Xml element.
        /// </summary>
        /// <param name="element">The result definition Xml element.</param>
        /// <returns>The result definition.</returns>
        public static SurveyResultDefinition ReadResultDefinitionElement(XElement element)
        {
            return new SurveyResultDefinition { Result = element.Value, Ruleset = element.Attribute("ruleset").Value };
        }

        /// <summary>
        /// Reads the ruleset definition Xml element.
        /// </summary>
        /// <param name="element">The ruleset definition Xml element.</param>
        /// <returns>The ruleset definition.</returns>
        public static RulesetDefinition ReadRulesetDefinitionElement(XElement element)
        {
            var definition = new RulesetDefinition { Id = element.Attribute("id").Value };

            AddConditions(element, definition.Conditions);

            return definition;
        }

        /// <summary>
        /// Reads a conditions Xml elements and fills a list with the read conditions.
        /// </summary>
        /// <param name="element">The conditions' Xml element.</param>
        /// <param name="conditions">The conditions list.</param>
        private static void AddConditions(XElement element, List<Condition> conditions)
        {
            foreach (var conditionElement in element.Elements())
            {
                var condition = ConditionElementReader(conditionElement);

                if (condition != null)
                {
                    conditions.Add(condition);
                }
            }
        }

        /// <summary>
        /// Reads a condition Xml element.
        /// </summary>
        /// <param name="element">The condition Xml element.</param>
        /// <returns>The condition.</returns>
        private static Condition ConditionElementReader(XElement element)
        {
            if (string.Equals(element.Name.LocalName, "or", StringComparison.OrdinalIgnoreCase))
            {
                var condition = new OrCondition();

                AddConditions(element, condition.Conditions);

                return condition;
            }
            else if (string.Equals(element.Name.LocalName, "and", StringComparison.OrdinalIgnoreCase))
            {
                var condition = new AndCondition();

                AddConditions(element, condition.Conditions);

                return condition;
            }
            else if (string.Equals(element.Name.LocalName, "match", StringComparison.OrdinalIgnoreCase))
            {
                var condition = new MatchCondition { ExpectedValue = element.Value, Identifier = element.Attribute("identifier").Value };

                return condition;
            }
            else if (string.Equals(element.Name.LocalName, "optionChecked", StringComparison.OrdinalIgnoreCase))
            {
                var condition = new OptionCheckedCondition { ExpectedValue = element.Value, Identifier = element.Attribute("identifier").Value };

                return condition;
            }
            else if (string.Equals(element.Name.LocalName, "not", StringComparison.OrdinalIgnoreCase))
            {
                var condition = new NotCondition();

                AddConditions(element, condition.Conditions);

                return condition;
            }
            else if (string.Equals(element.Name.LocalName, "withinRange", StringComparison.OrdinalIgnoreCase))
            {
                var condition = new WithinRangeCondition() { Identifier = element.Attribute("identifier").Value };

                return condition;
            }
            else
            {
                return null;
            }
        }
    }
}
